"""Network monitoring module for WiFi connectivity detection."""

import asyncio
import logging
import subprocess
from typing import Optional, Callable, List
from enum import Enum
import socket
import struct
import time

logger = logging.getLogger(__name__)


class ConnectivityStatus(Enum):
    """Network connectivity status enumeration."""
    CONNECTED = "connected"
    DISCONNECTED = "disconnected"
    CONNECTING = "connecting"
    UNKNOWN = "unknown"


class NetworkMonitor:
    """Monitor network connectivity and WiFi status."""
    
    def __init__(self, 
                 check_interval: int = 60,
                 connectivity_timeout: int = 5,
                 connectivity_hosts: Optional[List[str]] = None):
        """
        Initialize network monitor.
        
        Args:
            check_interval: Interval between network checks in seconds
            connectivity_timeout: Timeout for connectivity tests in seconds
            connectivity_hosts: List of hosts to test connectivity
        """
        self.check_interval = check_interval
        self.connectivity_timeout = connectivity_timeout
        self.connectivity_hosts = connectivity_hosts or [
            "8.8.8.8",  # Google DNS
            "1.1.1.1",  # Cloudflare DNS
        ]
        self._status = ConnectivityStatus.UNKNOWN
        self._monitoring = False
        self._monitor_task: Optional[asyncio.Task] = None
        self._status_callbacks: List[Callable] = []
        self._last_check_time = 0
        self._consecutive_failures = 0
        
    async def start_monitoring(self):
        """Start network monitoring."""
        if self._monitoring:
            logger.warning("Network monitoring already started")
            return
            
        self._monitoring = True
        self._monitor_task = asyncio.create_task(self._monitor_loop())
        logger.info("Network monitoring started")
        
    async def stop_monitoring(self):
        """Stop network monitoring."""
        if not self._monitoring:
            return
            
        self._monitoring = False
        if self._monitor_task:
            self._monitor_task.cancel()
            try:
                await self._monitor_task
            except asyncio.CancelledError:
                pass
        logger.info("Network monitoring stopped")
        
    async def check_wifi_status(self) -> bool:
        """
        Check if WiFi is connected with valid IP address.
        
        Returns:
            True if WiFi is connected, False otherwise
        """
        try:
            # Check network interface status using nmcli
            result = await asyncio.create_subprocess_exec(
                'nmcli', 'connection', 'show', '--active',
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout, stderr = await result.communicate()
            
            if result.returncode == 0:
                output = stdout.decode('utf-8')
                # Check if any WiFi connection is active
                if 'wifi' in output.lower() or '802-11-wireless' in output.lower():
                    # Verify IP address
                    ip_result = await asyncio.create_subprocess_exec(
                        'ip', 'addr', 'show',
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE
                    )
                    ip_stdout, _ = await ip_result.communicate()
                    ip_output = ip_stdout.decode('utf-8')
                    
                    # Look for valid IPv4 address (not link-local)
                    import re
                    ipv4_pattern = r'inet (?!169\.254\.)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
                    if re.search(ipv4_pattern, ip_output):
                        logger.debug("WiFi connected with valid IP address")
                        return True
                        
        except FileNotFoundError:
            # nmcli not available, try alternative method
            return await self._check_wifi_status_alternative()
        except Exception as e:
            logger.error(f"Error checking WiFi status: {e}")
            
        return False
        
    async def _check_wifi_status_alternative(self) -> bool:
        """Alternative method to check WiFi status without nmcli."""
        try:
            # Check if we can get default gateway
            result = await asyncio.create_subprocess_exec(
                'ip', 'route', 'show', 'default',
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout, _ = await result.communicate()
            
            if result.returncode == 0 and stdout:
                # Has default route, likely connected
                return True
                
        except Exception as e:
            logger.error(f"Alternative WiFi check failed: {e}")
            
        return False
        
    async def test_connectivity(self, host: Optional[str] = None) -> bool:
        """
        Test network connectivity to a host.
        
        Args:
            host: Host to test connectivity, uses default if None
            
        Returns:
            True if connectivity test succeeds, False otherwise
        """
        test_host = host or self.connectivity_hosts[0]
        
        try:
            # Try ping first
            result = await asyncio.create_subprocess_exec(
                'ping', '-c', '1', '-W', str(self.connectivity_timeout), test_host,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout, stderr = await result.communicate()
            
            if result.returncode == 0:
                logger.debug(f"Connectivity test to {test_host} succeeded")
                return True
                
        except FileNotFoundError:
            # ping not available, try socket connection
            return await self._test_connectivity_socket(test_host)
        except Exception as e:
            logger.error(f"Connectivity test error: {e}")
            
        return False
        
    async def _test_connectivity_socket(self, host: str) -> bool:
        """Test connectivity using socket connection."""
        try:
            # Try to connect to host on port 53 (DNS)
            reader, writer = await asyncio.wait_for(
                asyncio.open_connection(host, 53),
                timeout=self.connectivity_timeout
            )
            writer.close()
            await writer.wait_closed()
            return True
        except (asyncio.TimeoutError, OSError, ConnectionError):
            return False
            
    async def verify_connectivity(self, num_checks: int = 3, 
                                 check_interval: int = 10) -> bool:
        """
        Verify network connectivity with multiple checks.
        
        Args:
            num_checks: Number of consecutive successful checks required
            check_interval: Interval between checks in seconds
            
        Returns:
            True if all connectivity checks pass, False otherwise
        """
        successful_checks = 0
        
        for i in range(num_checks):
            if i > 0:
                await asyncio.sleep(check_interval)
                
            # Test connectivity to multiple hosts
            success = False
            for host in self.connectivity_hosts:
                if await self.test_connectivity(host):
                    success = True
                    break
                    
            if success:
                successful_checks += 1
                logger.info(f"Connectivity check {successful_checks}/{num_checks} passed")
            else:
                logger.warning(f"Connectivity check failed, resetting counter")
                return False
                
        return True
        
    def register_status_callback(self, callback: Callable):
        """
        Register callback for status changes.
        
        Args:
            callback: Function to call on status change
        """
        if callback not in self._status_callbacks:
            self._status_callbacks.append(callback)
            
    def unregister_status_callback(self, callback: Callable):
        """
        Unregister status callback.
        
        Args:
            callback: Function to unregister
        """
        if callback in self._status_callbacks:
            self._status_callbacks.remove(callback)
            
    async def _monitor_loop(self):
        """Main monitoring loop."""
        logger.info("Starting network monitor loop")
        
        while self._monitoring:
            try:
                # Check WiFi status
                wifi_connected = await self.check_wifi_status()
                
                if wifi_connected:
                    # Test connectivity
                    connectivity_ok = await self.test_connectivity()
                    
                    if connectivity_ok:
                        if self._status != ConnectivityStatus.CONNECTED:
                            await self._update_status(ConnectivityStatus.CONNECTED)
                        self._consecutive_failures = 0
                    else:
                        self._consecutive_failures += 1
                        if self._consecutive_failures >= 3:
                            await self._update_status(ConnectivityStatus.DISCONNECTED)
                else:
                    await self._update_status(ConnectivityStatus.DISCONNECTED)
                    
                self._last_check_time = time.time()
                
            except Exception as e:
                logger.error(f"Error in monitor loop: {e}")
                
            # Wait for next check
            await asyncio.sleep(self.check_interval)
            
    async def _update_status(self, new_status: ConnectivityStatus):
        """Update connectivity status and notify callbacks."""
        if self._status != new_status:
            old_status = self._status
            self._status = new_status
            logger.info(f"Network status changed: {old_status.value} -> {new_status.value}")
            
            # Notify callbacks
            for callback in self._status_callbacks:
                try:
                    if asyncio.iscoroutinefunction(callback):
                        await callback(new_status)
                    else:
                        callback(new_status)
                except Exception as e:
                    logger.error(f"Error in status callback: {e}")
                    
    def get_status(self) -> ConnectivityStatus:
        """Get current connectivity status."""
        return self._status
        
    def get_last_check_time(self) -> float:
        """Get timestamp of last connectivity check."""
        return self._last_check_time